﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Mvc;
using System.Diagnostics.Contracts;
using System.Web;
using System.IO;

namespace FoundationLib.Web.Mvc
{
	/// <summary>
	/// Represents a binary action result.
	/// </summary>
	public class BinaryResult : ActionResult
	{
		/// <summary>
		/// The content type
		/// </summary>
		private string contentType;
		/// <summary>
		/// The path to the file containing the binary content
		/// </summary>
		private string path;
		/// <summary>
		/// The binary content
		/// </summary>
		private byte[] content;
		/// <summary>
		/// A flag indicating whether the result is to be 
		/// sent as an attachment rather than inline
		/// </summary>
		private bool isAttachment;
		/// <summary>
		/// The file name to be used when the result is sent 
		/// as an attachment
		/// </summary>
		private string filename;


		/// <summary>
		/// Creates a new <see cref="BinaryResult"/> instance.
		/// </summary>
		private BinaryResult()
		{
		}

		/// <summary>
		/// Creates a new <see cref="BinaryResult"/> instance.
		/// </summary>
		/// <param name="contentType">The content type</param>
		/// <param name="path">The absolute path to the binary content</param>
		/// <param name="isAttachment">A value indicating whether the result should be 
		/// sent as an attachment rather than inline</param>
		public BinaryResult(string contentType, string path, bool isAttachment)
			: this()
		{
			Contract.Requires(!String.IsNullOrWhiteSpace(path));
			Contract.Requires(!String.IsNullOrWhiteSpace(contentType));
			this.path = path;
			this.contentType = contentType;
			this.isAttachment = isAttachment;
			this.filename = Path.GetFileName(path);
		}

		/// <summary>
		/// Creates a new <see cref="BinaryResult"/> instance.
		/// </summary>
		/// <param name="contentType">The content type</param>
		/// <param name="path">The absolute path to the binary content</param>
		public BinaryResult(string contentType, string path)
			: this(contentType, path, false)
		{
		}

		/// <summary>
		/// Creates a new <see cref="BinaryResult"/> instance.
		/// </summary>
		/// <param name="contentType">The content type</param>
		/// <param name="content">The binary content</param>
		/// <param name="filename">The file name to be used for attachments</param>
		/// <param name="isAttachment">A flag indicating whether the content is sent as an attachment</param>
		public BinaryResult(string contentType, byte[] content, string filename, bool isAttachment)
			: this(contentType, content)
		{
			Contract.Requires(!String.IsNullOrWhiteSpace(filename));
			this.isAttachment = isAttachment;
			this.filename = filename;
		}

		/// <summary>
		/// Creates a new <see cref="BinaryResult"/> instance.
		/// </summary>
		/// <param name="contentType">The content type</param>
		/// <param name="content">The binary content</param>
		/// <param name="filename">The file name to be used for attachments</param>
		public BinaryResult(string contentType, byte[] content, string filename)
			: this(contentType, content, filename, false)
		{
		}

		/// <summary>
		/// Creates a new <see cref="BinaryResult"/> instance.
		/// </summary>
		/// <param name="contentType">The content type</param>
		/// <param name="content">The binary content</param>
		public BinaryResult(string contentType, byte[] content)
			: this()
		{
			Contract.Requires(!String.IsNullOrWhiteSpace(contentType));
			this.contentType = contentType;
			this.content = content;
		}

		/// <summary>
		/// Creates a new <see cref="BinaryResult"/> instance.
		/// </summary>
		/// <param name="contentType">The content type</param>
		/// <param name="path">The absolute path pointing to the file containing the binary content</param>
		/// <param name="filename">The file name to be used for the attachment</param>
		/// <param name="isAttachment">A value indicating whether the result is sent as an attachment</param>
		public BinaryResult(string contentType, string path, string filename, bool isAttachment)
			: this(contentType, path, isAttachment)
		{
			Contract.Requires(!String.IsNullOrWhiteSpace(filename));
			this.filename = filename;
		}

		/// <summary>
		/// Creates a new <see cref="BinaryResult"/> instance.
		/// </summary>
		/// <param name="contentType">The content type</param>
		/// <param name="path">The absolute path pointing to the file containing the binary result</param>
		/// <param name="filename">The file name to be used for the attachment</param>
		public BinaryResult(string contentType, string path, string filename)
			: this(contentType, path, filename, false)
		{
		}

		/// <see cref="ActionResult.ExecuteResult(ControllerContext)"/>
		public override void ExecuteResult(ControllerContext context)
		{
			HttpResponseBase response = context.HttpContext.Response;
			response.Clear();
			if (!context.HttpContext.Request.IsSecureConnection)
				response.Cache.SetCacheability(HttpCacheability.NoCache);
			response.ContentType = this.contentType;

			if (!String.IsNullOrWhiteSpace(this.filename))
			{
				response.Headers.Add("content-disposition",
					String.Format("{0};filename={1}", this.isAttachment ? "attachment" : "inline", this.filename));
			}

			if (this.content != null)
			{
				using (MemoryStream stream = new MemoryStream(this.content))
				{
					stream.WriteTo(response.OutputStream);
				}
			}
			else
			{
				response.TransmitFile(UrlHelper.GenerateContentUrl(this.path, context.HttpContext));
			}
		}
	}
}
